﻿// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision$</version>
// </file>

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;

using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Gui.CompletionWindow;

namespace ICSharpCode.TextEditor
{
	/// <summary>
	/// This class is used for a basic text area control
	/// </summary>
	[ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")]
	[ToolboxItem(true)]
	public class TextEditorControl : TextEditorControlBase
	{
        Splitter textAreaSplitter = null;

        TextAreaControl primaryTextArea = null;		
		TextAreaControl secondaryTextArea = null;        

        PrintDocument   printDocument = null;

        protected Panel textAreaPanel = new Panel();

        [Browsable(false)]
		public PrintDocument PrintDocument {
			get {
				if (printDocument == null) {
					printDocument = new PrintDocument();
					printDocument.BeginPrint += new PrintEventHandler(this.BeginPrint);
					printDocument.PrintPage  += new PrintPageEventHandler(this.PrintPage);
				}
				return printDocument;
			}
		}

        TextAreaControl activeTextAreaControl;

        public TextAreaControl PrimaryTextArea
        {
            get { return primaryTextArea; }
        }

        public TextAreaControl SecondaryTextArea
        {
            get { return secondaryTextArea; }
        }

        public event EventHandler ActiveTextAreaControlChanged;

        public override TextAreaControl ActiveTextAreaControl
        {
            get
            {
                return activeTextAreaControl;
            }
        }

        public TextEditorControl()
        {
            BackColor = Color.FromArgb(255, 255, 255);
            SetStyle(ControlStyles.ContainerControl, true);
            textAreaPanel.Dock = DockStyle.Fill;

            Document = (new DocumentFactory()).CreateDocument();
            Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy();

            primaryTextArea = new TextAreaControl(this);           
            primaryTextArea.TextArea.GotFocus += delegate
            {
                SetActiveTextAreaControl(primaryTextArea);
            };
            primaryTextArea.Dock = DockStyle.Fill;

            textAreaPanel.Controls.Add(primaryTextArea);
            InitializeTextAreaControl(primaryTextArea);
            Controls.Add(textAreaPanel);
            activeTextAreaControl = primaryTextArea;
            ResizeRedraw = true;
            Document.UpdateCommited += new EventHandler(CommitUpdateRequested);

            OptionsChanged();
        }
		
		protected virtual void InitializeTextAreaControl(TextAreaControl newControl)
		{
		}

        public string GetWordBeforeCaret()
        {
            int start = TextUtilities.FindPrevWordStart(Document, ActiveTextAreaControl.TextArea.Caret.Offset);
            return Document.GetText(start, ActiveTextAreaControl.TextArea.Caret.Offset - start);
        }

        public override void ShowGuidelinesChanged()
        {
            bool _showGuidelines = document.TextEditorProperties.ShowGuidelines;
            if (secondaryTextArea != null)
            {
                primaryTextArea.SetTextChangedTimerEnable(_showGuidelines);
                secondaryTextArea.SetTextChangedTimerEnable(_showGuidelines);
            }
            else
            {
                activeTextAreaControl.SetTextChangedTimerEnable(_showGuidelines);
            }
        }

        public override void OptionsChanged()
        {
            primaryTextArea.OptionsChanged();
            if (secondaryTextArea != null)
            {
                secondaryTextArea.OptionsChanged();
            }
        }

        public override void BorderVisableChanged()
        {
            if (BorderVisable)
            {
                this.Padding = new Padding(1, 1, 1, 1);
            }
            else
            {
                this.Padding = new Padding(0, 0, 0, 0);
            }
        }

        public override void Invalidate()
        {
            this.activeTextAreaControl.Invalidate();
        }

        public override void OnTextAreaTextChanged(EventArgs e)
        {
            base.OnTextAreaTextChanged(e);
            if (Document != null &&
               Document.GuidelinesManager != null &&
               Document.TextEditorProperties.ShowGuidelines)
            {
                if (Document.TextEditorProperties.SplitState)
                {
                    primaryTextArea.DoTextAreaTextChanged(e);
                    secondaryTextArea.DoTextAreaTextChanged(e);
                }
                else
                {
                    activeTextAreaControl.DoTextAreaTextChanged(e);
                }
            }
        }        

        public override ICompletionData[] DoShowPromptBox(ShowPromptEventArgs e)
        {
            return base.DoShowPromptBox(e);
        }

        public string Split()
        {
            string result = string.Empty;
            if (secondaryTextArea == null)
            {
                secondaryTextArea = new TextAreaControl(this);
                secondaryTextArea.Dock = DockStyle.Bottom;
                secondaryTextArea.Height = Height / 2;               
                secondaryTextArea.TextArea.GotFocus += delegate
                {
                    SetActiveTextAreaControl(secondaryTextArea);
                };               
                textAreaSplitter = new Splitter();
                textAreaSplitter.BorderStyle = BorderStyle.FixedSingle;
                textAreaSplitter.Height = 8;
                textAreaSplitter.Dock = DockStyle.Bottom;
                textAreaPanel.Controls.Add(textAreaSplitter);
                textAreaPanel.Controls.Add(secondaryTextArea);
                InitializeTextAreaControl(secondaryTextArea);                
                secondaryTextArea.HScrollBar.BorderColor = activeTextAreaControl.HScrollBar.BorderColor;
                secondaryTextArea.VScrollBar.BorderColor = activeTextAreaControl.VScrollBar.BorderColor;
                secondaryTextArea.HScrollBar.ScrollBarRenderer = activeTextAreaControl.HScrollBar.ScrollBarRenderer;
                secondaryTextArea.VScrollBar.ScrollBarRenderer = activeTextAreaControl.VScrollBar.ScrollBarRenderer;
                secondaryTextArea.OptionsChanged();
                secondaryTextArea.SetTextChangedTimerEnable(Document.TextEditorProperties.ShowGuidelines);
          
                result = "SecondaryTextArea";
                Document.TextEditorProperties.SplitState = true;
                Document.GuidelinesManager.Update(primaryTextArea.TextArea);
                Document.GuidelinesManager.Update(secondaryTextArea.TextArea);

            }
            else
            {
                SetActiveTextAreaControl(primaryTextArea);

                textAreaPanel.Controls.Remove(secondaryTextArea);
                textAreaPanel.Controls.Remove(textAreaSplitter);

                secondaryTextArea.Dispose();
                textAreaSplitter.Dispose();
                secondaryTextArea = null;
                textAreaSplitter = null;                

                result = "PrimaryTextArea";
                Document.TextEditorProperties.SplitState = false;
                Document.GuidelinesManager.Update(primaryTextArea.TextArea);

            }
            return result;
        }
		
		[Browsable(false)]
		public bool EnableUndo {
			get {
				return Document.UndoStack.CanUndo;
			}
		}
		
		[Browsable(false)]
		public bool EnableRedo {
			get {
				return Document.UndoStack.CanRedo;
			}
		}

		public void Undo()
		{
			if (Document.ReadOnly) {
				return;
			}
			if (Document.UndoStack.CanUndo) {
				BeginUpdate();
				Document.UndoStack.Undo();
				
				Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
				this.primaryTextArea.TextArea.UpdateMatchingBracket();
				if (secondaryTextArea != null) {
					this.secondaryTextArea.TextArea.UpdateMatchingBracket();
				}
				EndUpdate();
			}
		}
		
		public void Redo()
		{
			if (Document.ReadOnly) {
				return;
			}
			if (Document.UndoStack.CanRedo) {
				BeginUpdate();
				Document.UndoStack.Redo();
				
				Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
				this.primaryTextArea.TextArea.UpdateMatchingBracket();
				if (secondaryTextArea != null) {
					this.secondaryTextArea.TextArea.UpdateMatchingBracket();
				}
				EndUpdate();
			}
		}
		
		public virtual void SetHighlighting(string name)
		{
            TextEditorScrollBarRenderer barRenderer = new WhiteScrollBarRenderer();
            Color scrollBarBorderColor = SystemColors.ControlLight;
            BackColor = Color.FromArgb(255, 255, 255);
            activeTextAreaControl.HScrollBar.BorderColor = scrollBarBorderColor;
            activeTextAreaControl.VScrollBar.BorderColor = scrollBarBorderColor;
            activeTextAreaControl.HScrollBar.ScrollBarRenderer = barRenderer;
            activeTextAreaControl.VScrollBar.ScrollBarRenderer = barRenderer;
            ThemesMapManager.GenerateThemeMap("Default");
            Document.ThemeName = "Default";
            Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(name);
		}

        public virtual void SetHighlighting(string theme, string name)
        {
            Color backgroundColor = Color.White;
            Color scrollBarBorderColor = SystemColors.ControlLight;
            TextEditorScrollBarRenderer barRenderer = new TextEditorScrollBarRenderer();
            switch (theme)
            {
                case "Black":
                    backgroundColor = Color.FromArgb(30, 30, 30);
                    scrollBarBorderColor = Color.FromArgb(93, 140, 201);
                    barRenderer = new BlackScrollBarRenderer();
                    break;
                default:
                    backgroundColor = Color.FromArgb(255, 255, 255);
                    scrollBarBorderColor = SystemColors.ControlLight;
                    barRenderer = new WhiteScrollBarRenderer();
                    break;
            }
            BackColor = backgroundColor;
            activeTextAreaControl.HScrollBar.BorderColor = scrollBarBorderColor;
            activeTextAreaControl.VScrollBar.BorderColor = scrollBarBorderColor;
            activeTextAreaControl.HScrollBar.ScrollBarRenderer = barRenderer;
            activeTextAreaControl.VScrollBar.ScrollBarRenderer = barRenderer;
            if (secondaryTextArea != null)
            {
                secondaryTextArea.HScrollBar.BorderColor = scrollBarBorderColor;
                secondaryTextArea.VScrollBar.BorderColor = scrollBarBorderColor;
                secondaryTextArea.HScrollBar.ScrollBarRenderer = barRenderer;
                secondaryTextArea.VScrollBar.ScrollBarRenderer = barRenderer;
            }
            ThemesMapManager.GenerateThemeMap(theme);
            Document.ThemeName = theme;
            Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(theme, name);
            activeTextAreaControl.TextArea.TextView.OptionsChanged();
            if (secondaryTextArea != null)
            {
                secondaryTextArea.TextArea.TextView.OptionsChanged();
            }
        }

        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            OnTextAreaTextChanged(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (printDocument != null)
                {
                    printDocument.BeginPrint -= new PrintEventHandler(this.BeginPrint);
                    printDocument.PrintPage -= new PrintPageEventHandler(this.PrintPage);
                    printDocument = null;
                }
                Document.UndoStack.ClearAll();
                Document.UpdateCommited -= new EventHandler(CommitUpdateRequested);
                if (textAreaPanel != null)
                {
                    if (secondaryTextArea != null)
                    {
                        secondaryTextArea.Dispose();
                        textAreaSplitter.Dispose();
                        secondaryTextArea = null;
                        textAreaSplitter = null;
                    }
                    if (primaryTextArea != null)
                    {
                        primaryTextArea.Dispose();
                    }
                    textAreaPanel.Dispose();
                    textAreaPanel = null;
                }
            }
            base.Dispose(disposing);
        }
		
		#region Update Methods
		public override void EndUpdate()
		{
			base.EndUpdate();
			Document.CommitUpdate();
			if (!IsInUpdate) {
				ActiveTextAreaControl.Caret.OnEndUpdate();
			}
		}
		
		void CommitUpdateRequested(object sender, EventArgs e)
		{
			if (IsInUpdate) {
				return;
			}
			foreach (TextAreaUpdate update in Document.UpdateQueue) {
				switch (update.TextAreaUpdateType) {
					case TextAreaUpdateType.PositionToEnd:
						this.primaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
						if (this.secondaryTextArea != null) {
							this.secondaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
						}
						break;
					case TextAreaUpdateType.PositionToLineEnd:
					case TextAreaUpdateType.SingleLine:
						this.primaryTextArea.TextArea.UpdateLine(update.Position.Y);
						if (this.secondaryTextArea != null) {
							this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y);
						}
						break;
					case TextAreaUpdateType.SinglePosition:
						this.primaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
						if (this.secondaryTextArea != null) {
							this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
						}
						break;
					case TextAreaUpdateType.LinesBetween:
						this.primaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
						if (this.secondaryTextArea != null) {
							this.secondaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
						}
						break;
					case TextAreaUpdateType.WholeTextArea:
						this.primaryTextArea.TextArea.Invalidate();
						if (this.secondaryTextArea != null) {
							this.secondaryTextArea.TextArea.Invalidate();
						}
						break;
				}
			}
			Document.UpdateQueue.Clear();
//			this.primaryTextArea.TextArea.Update();
//			if (this.secondaryTextArea != null) {
//				this.secondaryTextArea.TextArea.Update();
//			}
		}
		#endregion
		
		#region Printing routines
		int          curLineNr = 0;
		float        curTabIndent = 0;
		StringFormat printingStringFormat;

        void BeginPrint(object sender, PrintEventArgs ev)
        {
            curLineNr = 0;
            printingStringFormat = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();

            // 100 should be enough for everyone ...err ?
            float[] tabStops = new float[100];
            for (int i = 0; i < tabStops.Length; ++i)
            {
                tabStops[i] = TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth;
            }

            printingStringFormat.SetTabStops(0, tabStops);
        }

        void Advance(ref float x, ref float y, float maxWidth, float size, float fontHeight)
        {
            if (x + size < maxWidth)
            {
                x += size;
            }
            else
            {
                x = curTabIndent;
                y += fontHeight;
            }
        }

        // btw. I hate source code duplication ... but this time I don't care !!!!
        float MeasurePrintingHeight(Graphics g, LineSegment line, float maxWidth)
        {
            float xPos = 0;
            float yPos = 0;
            float fontHeight = Font.GetHeight(g);
            //			bool  gotNonWhitespace = false;
            curTabIndent = 0;
            FontContainer fontContainer = TextEditorProperties.FontContainer;
            foreach (TextWord word in line.Words)
            {
                switch (word.Type)
                {
                    case TextWordType.Space:
                        Advance(ref xPos, ref yPos, maxWidth, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
                        //						if (!gotNonWhitespace) {
                        //							curTabIndent = xPos;
                        //						}
                        break;
                    case TextWordType.Tab:
                        Advance(ref xPos, ref yPos, maxWidth, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
                        //						if (!gotNonWhitespace) {
                        //							curTabIndent = xPos;
                        //						}
                        break;
                    case TextWordType.Word:
                        //						if (!gotNonWhitespace) {
                        //							gotNonWhitespace = true;
                        //							curTabIndent    += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
                        //						}
                        SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(maxWidth, fontHeight * 100), printingStringFormat);
                        Advance(ref xPos, ref yPos, maxWidth, drawingSize.Width, fontHeight);
                        break;
                }
            }
            return yPos + fontHeight;
        }

        void DrawLine(Graphics g, LineSegment line, float yPos, RectangleF margin)
        {
            float xPos = 0;
            float fontHeight = Font.GetHeight(g);
            //			bool  gotNonWhitespace = false;
            curTabIndent = 0;

            FontContainer fontContainer = TextEditorProperties.FontContainer;
            foreach (TextWord word in line.Words)
            {
                switch (word.Type)
                {
                    case TextWordType.Space:
                        Advance(ref xPos, ref yPos, margin.Width, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
                        //						if (!gotNonWhitespace) {
                        //							curTabIndent = xPos;
                        //						}
                        break;
                    case TextWordType.Tab:
                        Advance(ref xPos, ref yPos, margin.Width, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
                        //						if (!gotNonWhitespace) {
                        //							curTabIndent = xPos;
                        //						}
                        break;
                    case TextWordType.Word:
                        //						if (!gotNonWhitespace) {
                        //							gotNonWhitespace = true;
                        //							curTabIndent    += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
                        //						}
                        g.DrawString(word.Word, word.GetFont(fontContainer), BrushRegistry.GetBrush(word.Color), xPos + margin.X, yPos);
                        SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(margin.Width, fontHeight * 100), printingStringFormat);
                        Advance(ref xPos, ref yPos, margin.Width, drawingSize.Width, fontHeight);
                        break;
                }
            }
        }

        void PrintPage(object sender, PrintPageEventArgs ev)
        {
            Graphics g = ev.Graphics;
            float yPos = ev.MarginBounds.Top;

            g.Clear(this.textAreaPanel.BackColor);

            while (curLineNr < Document.TotalNumberOfLines)
            {
                LineSegment curLine = Document.GetLineSegment(curLineNr);
                if (curLine.Words != null)
                {
                    float drawingHeight = MeasurePrintingHeight(g, curLine, ev.MarginBounds.Width);
                    if (drawingHeight + yPos > ev.MarginBounds.Bottom)
                    {
                        break;
                    }

                    DrawLine(g, curLine, yPos, ev.MarginBounds);
                    yPos += drawingHeight;
                }
                ++curLineNr;
            }

            // If more lines exist, print another page.
            ev.HasMorePages = curLineNr < Document.TotalNumberOfLines;
        }

        #endregion

        internal void SetActiveTextAreaControl(TextAreaControl value)
        {
            if (activeTextAreaControl != value)
            {
                activeTextAreaControl = value;

                if (ActiveTextAreaControlChanged != null)
                {
                    ActiveTextAreaControlChanged(this, EventArgs.Empty);
                }
            }
        }

    }
}
